package view;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.MouseInfo;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.prefs.Preferences;

import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;
import javax.swing.filechooser.FileNameExtensionFilter;

import model.commands.Command;
import model.commands.flow.Script;
import model.parser.ScriptPersistor;

import org.jdesktop.swingx.JXTaskPaneContainer;

import resources.ResourcesFactory;

public class AutoUITestFrame extends JFrame implements PropertyChangeListener {

	private static final long MINIMIZING_DELAY = 10L;
	private static final long serialVersionUID = 5520392453142012323L;
	private static final String APP_TITLE = "Auto UI Test";
	protected static final String LAST_PATH_USED = "LAST_PATH_USED";
	public static final int ROBOT_AUTO_DELAY = 20;
	private static final long DELAY_BETWEEN_SCRIPTS = 10;
	private JButton runButton;
	private JButton stopButton;
	private JButton pauseButton;
	private JButton resumeButton;
	private JButton openButton;
	private JFileChooser fileChooser;
	private Preferences preferences;
	private Script currentScript;
	private List<Script> scripts;
	private Robot robot;
	private JPanel playPauseCards;
	private JXTaskPaneContainer taskPaneContainer;
	private JButton clearButton;
	private int currentScriptIndex = 0;
	private JSpinner autoDelaySpinner;
	private boolean isRunningIndividually;
	protected boolean stoppedBulkExecution = false;
	private JLabel mousePositionLabel;

	public AutoUITestFrame() {
		// User preferences
		preferences = Preferences.userRoot().node(this.getClass().getName());
		scripts = new LinkedList<Script>();
		try {
			robot = new Robot();
		} catch (AWTException e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(AutoUITestFrame.this,
					"Error loading the application.", "Creation Error",
					JOptionPane.ERROR_MESSAGE);
		}
		createUI();
		startMousePositionThread();
		pack();
		this.setLocationRelativeTo(null);
	}

	private void createUI() {
		this.setTitle(APP_TITLE);
		setIconImage(ResourcesFactory.getAppIcon().getImage());
		setLayout(new BorderLayout(0, 0));
		setPreferredSize(new Dimension(340, 440));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		createMenu();
		createTopPanel();
		createCenterPanel();
		createBottomPanel();
	}

	private void createBottomPanel() {
		JPanel bottomPanel = new JPanel(new BorderLayout());
		JPanel buttonsPanel = new JPanel(new FlowLayout());
		buttonsPanel.setBackground(Color.decode("#677ED9"));
		buttonsPanel.setBorder(new LineBorder(Color.decode("#B8CFE5"), 1));
		
		// Stop Button
		stopButton = new JButton("Stop", ResourcesFactory.getStopIcon());
		stopButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				resetRunningButtons();
				currentScript.stop();
				stoppedBulkExecution  = true;
			}
		});
		stopButton.setEnabled(false);
		buttonsPanel.add(stopButton);

		// Run Button
		runButton = new JButton("Run!", ResourcesFactory.getPlayIcon());
		runButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent arg0) {
				runAllAction();
			}

			
		});

		// Pause Button
		pauseButton = new JButton("Pause", ResourcesFactory.getPauseIcon());
		pauseButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent evt) {
				CardLayout cl = (CardLayout) (playPauseCards.getLayout());
				cl.show(playPauseCards, "RESUME_BUTTON");
				currentScript.pause();
			}
		});

		// resumeButton
		resumeButton = new JButton("Resume", ResourcesFactory.getPlayIcon());
		resumeButton.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent evt) {
				CardLayout cl = (CardLayout) (playPauseCards.getLayout());
				cl.show(playPauseCards, "PAUSE_BUTTON");
				// Minimizes the main frame before run
				setExtendedState(Frame.ICONIFIED);
				// runScript must be executed in other thread than the event
				// dispatcher thread
				Thread t = new Thread(new Runnable() {

					@Override
					public void run() {
						// waits for minimizing main frame
						try {
							Thread.sleep(MINIMIZING_DELAY);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						currentScript.resume();
					}
				});
				t.start();
			}
		});

		playPauseCards = new JPanel(new CardLayout());
		playPauseCards.add(runButton, "RUN_BUTTON");
		playPauseCards.add(pauseButton, "PAUSE_BUTTON");
		playPauseCards.add(resumeButton, "RESUME_BUTTON");
		buttonsPanel.add(playPauseCards);
		
		JPanel messagePanel = new JPanel(new BorderLayout());
		mousePositionLabel = new JLabel("X=0, Y=0");
		mousePositionLabel.setForeground(Color.WHITE);
		messagePanel.add(mousePositionLabel,BorderLayout.EAST);
		messagePanel.setOpaque(true);
		messagePanel.setBackground(Color.decode("#677ED9"));
		messagePanel.setBorder(new LineBorder(Color.decode("#B8CFE5"), 1));
		
		bottomPanel.add(messagePanel, BorderLayout.SOUTH);
		
		bottomPanel.add(buttonsPanel, BorderLayout.CENTER);
		this.add(bottomPanel, BorderLayout.SOUTH);
	}

	private void createCenterPanel() {
		// a container to put all JXTaskPane together
		taskPaneContainer = new JXTaskPaneContainer();
		taskPaneContainer.setBackground(Color.decode("#677ED9"));
		// add this taskPane to the taskPaneContainer
		JScrollPane taskPaneContainerScrollPane = new JScrollPane(taskPaneContainer);
		taskPaneContainerScrollPane.setBorder(new LineBorder(Color.decode("#B8CFE5"), 1));
		add(taskPaneContainerScrollPane, BorderLayout.CENTER);
	}

	private void createTopPanel() {
		JPanel topPanel = new JPanel(new FlowLayout());
		topPanel.setBackground(Color.decode("#677ED9"));
		topPanel.setBorder(new LineBorder(Color.decode("#B8CFE5"), 1));
		
		fileChooser = new JFileChooser();
		fileChooser.setFileFilter(new FileNameExtensionFilter(
				"Script XML Files (.xml)", "xml"));
		fileChooser.setAcceptAllFileFilterUsed(false);
		fileChooser.setFileHidingEnabled(true);
		fileChooser.setMultiSelectionEnabled(true);
		String lastPathUsed = preferences.get(LAST_PATH_USED, null);
		if (lastPathUsed != null) {
			fileChooser.setCurrentDirectory(new File(lastPathUsed));
		}
		// Open Button
		openButton = new JButton("Open", ResourcesFactory.getOpenIcon());
		openButton.setToolTipText("Open Script File");

		openButton.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent arg0) {
				chooseScriptFile();
			}
		});
		topPanel.add(openButton);
		
		// Clear Button
		clearButton = new JButton("Clear", ResourcesFactory.getClearIcon());
		clearButton.setToolTipText("Removes all ths Script Files");

		clearButton.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent arg0) {
				clearScripts();
			}
		});
		topPanel.add(clearButton);
		
		//auto delay
		JPanel auxPanel = new JPanel(new FlowLayout());
		JLabel delayLabel = new JLabel("Delay");
		delayLabel.setForeground(Color.white);
		auxPanel.add(delayLabel);
		auxPanel.setOpaque(false);
		autoDelaySpinner = new JSpinner(new SpinnerNumberModel(ROBOT_AUTO_DELAY, 0, 9999, 10));
		autoDelaySpinner.setPreferredSize(new Dimension(50,25));
		autoDelaySpinner.setToolTipText("Delay between actions.");
		auxPanel.add(autoDelaySpinner);
		JLabel msLabel = new JLabel("ms.");
		msLabel.setForeground(Color.white);
		auxPanel.add(msLabel);
		topPanel.add(auxPanel);
		
		add(topPanel, BorderLayout.NORTH);
	}

	protected void clearScripts() {
		currentScriptIndex = 0;
		scripts.clear();
		currentScript = null;
		taskPaneContainer.removeAll();
		taskPaneContainer.updateUI();
		resetRunningButtons();
	}

	protected void chooseScriptFile() {
		int retVal = fileChooser.showOpenDialog(AutoUITestFrame.this);
		if (retVal == JFileChooser.APPROVE_OPTION) {
			for(File selectedFile : fileChooser.getSelectedFiles()){
				openScriptFile(selectedFile);
			}
		}
	}

	private void openScriptFile(File file) {
		try {
			ScriptPersistor scriptPersistor = new ScriptPersistor();
			Script newScript = scriptPersistor.loadFromFile(file);
			newScript.addEventListener(Command.EXECUTION_FINISHED, this);
			createIndividualScriptPanel(newScript,scripts.size());
			scripts.add(newScript);
			// saves last file used in user preferences
			preferences.put(LAST_PATH_USED, file.getAbsolutePath());
		} catch (Exception e) {
			String errorMessage = "Error loading script file: "
					+ (file != null ? file.getName() : "");
			JOptionPane.showMessageDialog(AutoUITestFrame.this, errorMessage,
					"Error in script File", JOptionPane.ERROR_MESSAGE);
			e.printStackTrace();
			throw new RuntimeException(errorMessage);
		}
	}

	private void createIndividualScriptPanel(Script newScript,int index) {
		//Creates the scriptPanel
		IndividualScriptPanel individualScriptPanel = new IndividualScriptPanel(newScript,index);
		individualScriptPanel.addPropertyChangeListener(this);
		addScriptPAnel(individualScriptPanel);
	}

	private void addScriptPAnel(IndividualScriptPanel individualScriptPanel) {
		taskPaneContainer.add(individualScriptPanel);
		taskPaneContainer.updateUI();
	}

	protected void runAllScripts() {
		try {
			isRunningIndividually = false;
			robot.setAutoDelay((Integer) autoDelaySpinner.getValue());
			for(currentScriptIndex = 0; currentScriptIndex < scripts.size() ; currentScriptIndex++){
				if(stoppedBulkExecution){
					break;
				}
				currentScript = scripts.get(currentScriptIndex);
				currentScript.setRobot(robot);
				currentScript.execute();
				try {
					Thread.sleep(DELAY_BETWEEN_SCRIPTS);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(AutoUITestFrame.this,
					"Error trying to execute the current script: " + currentScript.getName(), "Script Error",
					JOptionPane.ERROR_MESSAGE);
		}
	}
	
	protected void runIndividualScript(int index) {
		try {
			isRunningIndividually=true;
			robot.setAutoDelay((Integer) autoDelaySpinner.getValue());
			currentScriptIndex = index;
			currentScript = scripts.get(currentScriptIndex);
			currentScript.setRobot(robot);
			currentScript.execute();
			try {
				Thread.sleep(DELAY_BETWEEN_SCRIPTS);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} catch (Exception e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(AutoUITestFrame.this,
					"Error trying to execute the current script: " + currentScript.getName(), "Script Error",
					JOptionPane.ERROR_MESSAGE);
		}
	}

	private void createMenu() {
		JMenuBar menuBar = new JMenuBar();
		// FILE
		JMenu fileMenu = new JMenu("File");
		menuBar.add(fileMenu);

		// Open
		JMenuItem openMenuItem = new JMenuItem("Open...");
		openMenuItem.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				chooseScriptFile();
			}

		});
		fileMenu.add(openMenuItem);

		// SEPARATOR
		fileMenu.add(new JSeparator());

		// Exit
		JMenuItem exitMenuItem = new JMenuItem("Exit");
		exitMenuItem.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}

		});
		fileMenu.add(exitMenuItem);

		// HELP
		JMenu helpMenu = new JMenu("Help");
		menuBar.add(helpMenu);

		// About
		JMenuItem aboutMenuItem = new JMenuItem("About AutoUITest...");
		aboutMenuItem.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				new AboutDialog(AutoUITestFrame.this).setVisible(true);
			}
		});
		helpMenu.add(aboutMenuItem);
		this.setJMenuBar(menuBar);

	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if(evt.getPropertyName().equals(Command.EXECUTION_FINISHED)){
			//If it's the last script...
			if(currentScriptIndex == scripts.size()-1 || isRunningIndividually){
				resetRunningButtons();
				isRunningIndividually = false;
			}
		}else if(evt.getPropertyName().equals(IndividualScriptPanel.RUN_INDIVIDUAL_SCRIPT)){
			final int index = (Integer)evt.getNewValue();
			CardLayout cl = (CardLayout) (playPauseCards.getLayout());
			cl.show(playPauseCards, "PAUSE_BUTTON");
			stopButton.setEnabled(true);
			// Minimizes the main frame before run
			setExtendedState(Frame.ICONIFIED);
			// runScript must be executed in other thread than the event
			// dispatcher thread
			Thread t = new Thread(new Runnable() {

				@Override
				public void run() {
					// waits for minimizing main frame
					try {
						Thread.sleep(MINIMIZING_DELAY);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					runIndividualScript(index);
				}
			});
			t.start();
		}else if(evt.getPropertyName().equals(IndividualScriptPanel.REMOVE_INDIVIDUAL_SCRIPT)){
			int index = (Integer)evt.getNewValue();
			scripts.remove(index);
			recreateIndividualScriptPanels();
		}else if(evt.getPropertyName().equals(IndividualScriptPanel.MOVE_UP_INDIVIDUAL_SCRIPT)){
			int index = (Integer)evt.getNewValue();
			Script script = scripts.remove(index);
			scripts.add(index-1,script);
			recreateIndividualScriptPanels();
		}else if(evt.getPropertyName().equals(IndividualScriptPanel.MOVE_DOWN_INDIVIDUAL_SCRIPT)){
			int index = (Integer)evt.getNewValue();
			if(index == scripts.size()){
				return;
			}
			Script script = scripts.remove(index);
			scripts.add(index+1,script);
			recreateIndividualScriptPanels();
		}
	}

	private void resetRunningButtons() {
		// Script's execution finished
		CardLayout cl = (CardLayout) (playPauseCards.getLayout());
		cl.show(playPauseCards, "RUN_BUTTON");
		stopButton.setEnabled(false);
	}
	
	private void recreateIndividualScriptPanels(){
		taskPaneContainer.removeAll();
		taskPaneContainer.updateUI();
		int index = 0;
		for(Script script : scripts){
			createIndividualScriptPanel(script,index);
			index++;
		}
	}
	
	private void runAllAction() {
		if (scripts.size() == 0) {
			return;
		}
		CardLayout cl = (CardLayout) (playPauseCards.getLayout());
		cl.show(playPauseCards, "PAUSE_BUTTON");
		stopButton.setEnabled(true);
		stoppedBulkExecution = false;
		// Minimizes the main frame before run
		setExtendedState(Frame.ICONIFIED);
		// runScript must be executed in other thread than the event
		// dispatcher thread
		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				// waits for minimizing main frame
				try {
					Thread.sleep(MINIMIZING_DELAY);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				runAllScripts();
			}
		});
		t.start();
	}
	
	private void startMousePositionThread(){
		Thread thread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					mousePositionLabel.setText("X="+MouseInfo.getPointerInfo().getLocation().x+", Y="+MouseInfo.getPointerInfo().getLocation().y);
				}
			}
		});
		thread.start();
	}
	
	public static void main(final String[] args) {
		int key=KeyEvent.VK_T;
		System.out.println(key);
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				AutoUITestFrame frame = new AutoUITestFrame();
				frame.setVisible(true);
			}
		});
	}
}
